/*!

Holder - client side image placeholders
Version 2.9.7+5g5ho
© 2020 Ivan Malopinsky - https://imsky.co

Site:     http://holderjs.com
Issues:   https://github.com/imsky/holder/issues
License:  MIT

*/
;(function (window) {
  if (!window.document) return
  var document = window.document

  //https://github.com/inexorabletash/polyfill/blob/master/web.js
  if (!document.querySelectorAll) {
    document.querySelectorAll = function (selectors) {
      var style = document.createElement('style'),
        elements = [],
        element
      document.documentElement.firstChild.appendChild(style)
      document._qsa = []

      style.styleSheet.cssText =
        selectors +
        '{x-qsa:expression(document._qsa && document._qsa.push(this))}'
      window.scrollBy(0, 0)
      style.parentNode.removeChild(style)

      while (document._qsa.length) {
        element = document._qsa.shift()
        element.style.removeAttribute('x-qsa')
        elements.push(element)
      }
      document._qsa = null
      return elements
    }
  }

  if (!document.querySelector) {
    document.querySelector = function (selectors) {
      var elements = document.querySelectorAll(selectors)
      return elements.length ? elements[0] : null
    }
  }

  if (!document.getElementsByClassName) {
    document.getElementsByClassName = function (classNames) {
      classNames = String(classNames).replace(/^|\s+/g, '.')
      return document.querySelectorAll(classNames)
    }
  }

  //https://github.com/inexorabletash/polyfill
  // ES5 15.2.3.14 Object.keys ( O )
  // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
  if (!Object.keys) {
    Object.keys = function (o) {
      if (o !== Object(o)) {
        throw TypeError('Object.keys called on non-object')
      }
      var ret = [],
        p
      for (p in o) {
        if (Object.prototype.hasOwnProperty.call(o, p)) {
          ret.push(p)
        }
      }
      return ret
    }
  }

  // ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
  // From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
  if (!Array.prototype.forEach) {
    Array.prototype.forEach = function (fun /*, thisp */) {
      if (this === void 0 || this === null) {
        throw TypeError()
      }

      var t = Object(this)
      var len = t.length >>> 0
      if (typeof fun !== 'function') {
        throw TypeError()
      }

      var thisp = arguments[1],
        i
      for (i = 0; i < len; i++) {
        if (i in t) {
          fun.call(thisp, t[i], i, t)
        }
      }
    }
  }

  //https://github.com/inexorabletash/polyfill/blob/master/web.js
  ;(function (global) {
    var B64_ALPHABET =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
    global.atob =
      global.atob ||
      function (input) {
        input = String(input)
        var position = 0,
          output = [],
          buffer = 0,
          bits = 0,
          n

        input = input.replace(/\s/g, '')
        if (input.length % 4 === 0) {
          input = input.replace(/=+$/, '')
        }
        if (input.length % 4 === 1) {
          throw Error('InvalidCharacterError')
        }
        if (/[^+/0-9A-Za-z]/.test(input)) {
          throw Error('InvalidCharacterError')
        }

        while (position < input.length) {
          n = B64_ALPHABET.indexOf(input.charAt(position))
          buffer = (buffer << 6) | n
          bits += 6

          if (bits === 24) {
            output.push(String.fromCharCode((buffer >> 16) & 0xff))
            output.push(String.fromCharCode((buffer >> 8) & 0xff))
            output.push(String.fromCharCode(buffer & 0xff))
            bits = 0
            buffer = 0
          }
          position += 1
        }

        if (bits === 12) {
          buffer = buffer >> 4
          output.push(String.fromCharCode(buffer & 0xff))
        } else if (bits === 18) {
          buffer = buffer >> 2
          output.push(String.fromCharCode((buffer >> 8) & 0xff))
          output.push(String.fromCharCode(buffer & 0xff))
        }

        return output.join('')
      }

    global.btoa =
      global.btoa ||
      function (input) {
        input = String(input)
        var position = 0,
          out = [],
          o1,
          o2,
          o3,
          e1,
          e2,
          e3,
          e4

        if (/[^\x00-\xFF]/.test(input)) {
          throw Error('InvalidCharacterError')
        }

        while (position < input.length) {
          o1 = input.charCodeAt(position++)
          o2 = input.charCodeAt(position++)
          o3 = input.charCodeAt(position++)

          // 111111 112222 222233 333333
          e1 = o1 >> 2
          e2 = ((o1 & 0x3) << 4) | (o2 >> 4)
          e3 = ((o2 & 0xf) << 2) | (o3 >> 6)
          e4 = o3 & 0x3f

          if (position === input.length + 2) {
            e3 = 64
            e4 = 64
          } else if (position === input.length + 1) {
            e4 = 64
          }

          out.push(
            B64_ALPHABET.charAt(e1),
            B64_ALPHABET.charAt(e2),
            B64_ALPHABET.charAt(e3),
            B64_ALPHABET.charAt(e4)
          )
        }

        return out.join('')
      }
  })(window)

  //https://gist.github.com/jimeh/332357
  if (!Object.prototype.hasOwnProperty) {
    /*jshint -W001, -W103 */
    Object.prototype.hasOwnProperty = function (prop) {
      var proto = this.__proto__ || this.constructor.prototype
      return prop in this && (!(prop in proto) || proto[prop] !== this[prop])
    }
    /*jshint +W001, +W103 */
  }

  // @license http://opensource.org/licenses/MIT
  // copyright Paul Irish 2015

  // Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill
  //   github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js
  // as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values

  // if you want values similar to what you'd get with real perf.now, place this towards the head of the page
  // but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed

  ;(function () {
    if ('performance' in window === false) {
      window.performance = {}
    }

    Date.now =
      Date.now ||
      function () {
        // thanks IE8
        return new Date().getTime()
      }

    if ('now' in window.performance === false) {
      var nowOffset = Date.now()

      if (performance.timing && performance.timing.navigationStart) {
        nowOffset = performance.timing.navigationStart
      }

      window.performance.now = function now() {
        return Date.now() - nowOffset
      }
    }
  })()

  //requestAnimationFrame polyfill for older Firefox/Chrome versions
  if (!window.requestAnimationFrame) {
    if (
      window.webkitRequestAnimationFrame &&
      window.webkitCancelAnimationFrame
    ) {
      //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-webkit.js
      ;(function (global) {
        global.requestAnimationFrame = function (callback) {
          return webkitRequestAnimationFrame(function () {
            callback(global.performance.now())
          })
        }

        global.cancelAnimationFrame = global.webkitCancelAnimationFrame
      })(window)
    } else if (
      window.mozRequestAnimationFrame &&
      window.mozCancelAnimationFrame
    ) {
      //https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-moz.js
      ;(function (global) {
        global.requestAnimationFrame = function (callback) {
          return mozRequestAnimationFrame(function () {
            callback(global.performance.now())
          })
        }

        global.cancelAnimationFrame = global.mozCancelAnimationFrame
      })(window)
    } else {
      ;(function (global) {
        global.requestAnimationFrame = function (callback) {
          return global.setTimeout(callback, 1000 / 60)
        }

        global.cancelAnimationFrame = global.clearTimeout
      })(window)
    }
  }
})(this)

;(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory()
  else if (typeof define === 'function' && define.amd) define([], factory)
  else if (typeof exports === 'object') exports['Holder'] = factory()
  else root['Holder'] = factory()
})(this, function () {
  return /******/ (function (modules) {
    // webpackBootstrap
    /******/ // The module cache
    /******/ var installedModules = {}

    /******/ // The require function
    /******/ function __webpack_require__(moduleId) {
      /******/ // Check if module is in cache
      /******/ if (installedModules[moduleId])
        /******/ return installedModules[moduleId].exports

      /******/ // Create a new module (and put it into the cache)
      /******/ var module = (installedModules[moduleId] = {
        /******/ exports: {},
        /******/ id: moduleId,
        /******/ loaded: false,
        /******/
      })

      /******/ // Execute the module function
      /******/ modules[moduleId].call(
        module.exports,
        module,
        module.exports,
        __webpack_require__
      )

      /******/ // Flag the module as loaded
      /******/ module.loaded = true

      /******/ // Return the exports of the module
      /******/ return module.exports
      /******/
    }

    /******/ // expose the modules object (__webpack_modules__)
    /******/ __webpack_require__.m = modules

    /******/ // expose the module cache
    /******/ __webpack_require__.c = installedModules

    /******/ // __webpack_public_path__
    /******/ __webpack_require__.p = ''

    /******/ // Load entry module and return exports
    /******/ return __webpack_require__(0)
    /******/
  })(
    /************************************************************************/
    /******/ [
      /* 0 */
      /***/ function (module, exports, __webpack_require__) {
        /*
      Holder.js - client side image placeholders
      (c) 2012-2020 Ivan Malopinsky - https://imsky.co
      */

        module.exports = __webpack_require__(1)

        /***/
      },
      /* 1 */
      /***/ function (module, exports, __webpack_require__) {
        /* WEBPACK VAR INJECTION */ ;(function (global) {
          /*
      Holder.js - client side image placeholders
      (c) 2012-2020 Ivan Malopinsky - http://imsky.co
      */

          //Libraries and functions
          var onDomReady = __webpack_require__(2)
          var querystring = __webpack_require__(3)

          var SceneGraph = __webpack_require__(6)
          var utils = __webpack_require__(7)
          var SVG = __webpack_require__(8)
          var DOM = __webpack_require__(9)
          var Color = __webpack_require__(10)
          var constants = __webpack_require__(11)

          var svgRenderer = __webpack_require__(12)
          var sgCanvasRenderer = __webpack_require__(15)

          var extend = utils.extend
          var dimensionCheck = utils.dimensionCheck

          //Constants and definitions
          var SVG_NS = constants.svg_ns

          var Holder = {
            version: constants.version,

            /**
             * Adds a theme to default settings
             *
             * @param {string} name Theme name
             * @param {Object} theme Theme object, with foreground, background, size, font, and fontweight properties.
             */
            addTheme: function (name, theme) {
              name != null &&
                theme != null &&
                (App.settings.themes[name] = theme)
              delete App.vars.cache.themeKeys
              return this
            },

            /**
             * Appends a placeholder to an element
             *
             * @param {string} src Placeholder URL string
             * @param el A selector or a reference to a DOM node
             */
            addImage: function (src, el) {
              //todo: use jquery fallback if available for all QSA references
              var nodes = DOM.getNodeArray(el)
              nodes.forEach(function (node) {
                var img = DOM.newEl('img')
                var domProps = {}
                domProps[App.setup.dataAttr] = src
                DOM.setAttr(img, domProps)
                node.appendChild(img)
              })
              return this
            },

            /**
             * Sets whether or not an image is updated on resize.
             * If an image is set to be updated, it is immediately rendered.
             *
             * @param {Object} el Image DOM element
             * @param {Boolean} value Resizable update flag value
             */
            setResizeUpdate: function (el, value) {
              if (el.holderData) {
                el.holderData.resizeUpdate = !!value
                if (el.holderData.resizeUpdate) {
                  updateResizableElements(el)
                }
              }
            },

            /**
             * Runs Holder with options. By default runs Holder on all images with "holder.js" in their source attributes.
             *
             * @param {Object} userOptions Options object, can contain domain, themes, images, and bgnodes properties
             */
            run: function (userOptions) {
              //todo: split processing into separate queues
              userOptions = userOptions || {}
              var engineSettings = {}
              var options = extend(App.settings, userOptions)

              App.vars.preempted = true
              App.vars.dataAttr = options.dataAttr || App.setup.dataAttr

              engineSettings.renderer = options.renderer
                ? options.renderer
                : App.setup.renderer
              if (
                App.setup.renderers
                  .join(',')
                  .indexOf(engineSettings.renderer) === -1
              ) {
                engineSettings.renderer = App.setup.supportsSVG
                  ? 'svg'
                  : App.setup.supportsCanvas
                  ? 'canvas'
                  : 'html'
              }

              var images = DOM.getNodeArray(options.images)
              var bgnodes = DOM.getNodeArray(options.bgnodes)
              var stylenodes = DOM.getNodeArray(options.stylenodes)
              var objects = DOM.getNodeArray(options.objects)

              engineSettings.stylesheets = []
              engineSettings.svgXMLStylesheet = true
              engineSettings.noFontFallback = !!options.noFontFallback
              engineSettings.noBackgroundSize = !!options.noBackgroundSize

              stylenodes.forEach(function (styleNode) {
                if (
                  styleNode.attributes.rel &&
                  styleNode.attributes.href &&
                  styleNode.attributes.rel.value == 'stylesheet'
                ) {
                  var href = styleNode.attributes.href.value
                  //todo: write isomorphic relative-to-absolute URL function
                  var proxyLink = DOM.newEl('a')
                  proxyLink.href = href
                  var stylesheetURL =
                    proxyLink.protocol +
                    '//' +
                    proxyLink.host +
                    proxyLink.pathname +
                    proxyLink.search
                  engineSettings.stylesheets.push(stylesheetURL)
                }
              })

              bgnodes.forEach(function (bgNode) {
                //Skip processing background nodes if getComputedStyle is unavailable, since only modern browsers would be able to use canvas or SVG to render to background
                if (!global.getComputedStyle) return
                var backgroundImage = global
                  .getComputedStyle(bgNode, null)
                  .getPropertyValue('background-image')
                var dataBackgroundImage = bgNode.getAttribute(
                  'data-background-src'
                )
                var rawURL = dataBackgroundImage || backgroundImage

                var holderURL = null
                var holderString = options.domain + '/'
                var holderStringIndex = rawURL.indexOf(holderString)

                if (holderStringIndex === 0) {
                  holderURL = rawURL
                } else if (holderStringIndex === 1 && rawURL[0] === '?') {
                  holderURL = rawURL.slice(1)
                } else {
                  var fragment = rawURL
                    .substr(holderStringIndex)
                    .match(/([^\"]*)"?\)/)
                  if (fragment !== null) {
                    holderURL = fragment[1]
                  } else if (rawURL.indexOf('url(') === 0) {
                    throw 'Holder: unable to parse background URL: ' + rawURL
                  }
                }

                if (holderURL) {
                  var holderFlags = parseURL(holderURL, options)
                  if (holderFlags) {
                    prepareDOMElement({
                      mode: 'background',
                      el: bgNode,
                      flags: holderFlags,
                      engineSettings: engineSettings,
                    })
                  }
                }
              })

              objects.forEach(function (object) {
                var objectAttr = {}

                try {
                  objectAttr.data = object.getAttribute('data')
                  objectAttr.dataSrc = object.getAttribute(App.vars.dataAttr)
                } catch (e) {}

                var objectHasSrcURL =
                  objectAttr.data != null &&
                  objectAttr.data.indexOf(options.domain) === 0
                var objectHasDataSrcURL =
                  objectAttr.dataSrc != null &&
                  objectAttr.dataSrc.indexOf(options.domain) === 0

                if (objectHasSrcURL) {
                  prepareImageElement(
                    options,
                    engineSettings,
                    objectAttr.data,
                    object
                  )
                } else if (objectHasDataSrcURL) {
                  prepareImageElement(
                    options,
                    engineSettings,
                    objectAttr.dataSrc,
                    object
                  )
                }
              })

              images.forEach(function (image) {
                var imageAttr = {}

                try {
                  imageAttr.src = image.getAttribute('src')
                  imageAttr.dataSrc = image.getAttribute(App.vars.dataAttr)
                  imageAttr.rendered = image.getAttribute(
                    'data-holder-rendered'
                  )
                } catch (e) {}

                var imageHasSrc = imageAttr.src != null
                var imageHasDataSrcURL =
                  imageAttr.dataSrc != null &&
                  imageAttr.dataSrc.indexOf(options.domain) === 0
                var imageRendered =
                  imageAttr.rendered != null && imageAttr.rendered == 'true'

                if (imageHasSrc) {
                  if (imageAttr.src.indexOf(options.domain) === 0) {
                    prepareImageElement(
                      options,
                      engineSettings,
                      imageAttr.src,
                      image
                    )
                  } else if (imageHasDataSrcURL) {
                    //Image has a valid data-src and an invalid src
                    if (imageRendered) {
                      //If the placeholder has already been render, re-render it
                      prepareImageElement(
                        options,
                        engineSettings,
                        imageAttr.dataSrc,
                        image
                      )
                    } else {
                      //If the placeholder has not been rendered, check if the image exists and render a fallback if it doesn't
                      ;(function (
                        src,
                        options,
                        engineSettings,
                        dataSrc,
                        image
                      ) {
                        utils.imageExists(src, function (exists) {
                          if (!exists) {
                            prepareImageElement(
                              options,
                              engineSettings,
                              dataSrc,
                              image
                            )
                          }
                        })
                      })(
                        imageAttr.src,
                        options,
                        engineSettings,
                        imageAttr.dataSrc,
                        image
                      )
                    }
                  }
                } else if (imageHasDataSrcURL) {
                  prepareImageElement(
                    options,
                    engineSettings,
                    imageAttr.dataSrc,
                    image
                  )
                }
              })

              return this
            },
          }

          var App = {
            settings: {
              domain: 'holder.js',
              images: 'img',
              objects: 'object',
              bgnodes: 'body .holderjs',
              stylenodes: 'head link.holderjs',
              themes: {
                gray: {
                  bg: '#EEEEEE',
                  fg: '#AAAAAA',
                },
                social: {
                  bg: '#3a5a97',
                  fg: '#FFFFFF',
                },
                industrial: {
                  bg: '#434A52',
                  fg: '#C2F200',
                },
                sky: {
                  bg: '#0D8FDB',
                  fg: '#FFFFFF',
                },
                vine: {
                  bg: '#39DBAC',
                  fg: '#1E292C',
                },
                lava: {
                  bg: '#F8591A',
                  fg: '#1C2846',
                },
              },
            },
            defaults: {
              size: 10,
              units: 'pt',
              scale: 1 / 16,
            },
          }

          /**
           * Processes provided source attribute and sets up the appropriate rendering workflow
           *
           * @private
           * @param options Instance options from Holder.run
           * @param renderSettings Instance configuration
           * @param src Image URL
           * @param el Image DOM element
           */
          function prepareImageElement(options, engineSettings, src, el) {
            var holderFlags = parseURL(
              src.substr(src.lastIndexOf(options.domain)),
              options
            )
            if (holderFlags) {
              prepareDOMElement({
                mode: null,
                el: el,
                flags: holderFlags,
                engineSettings: engineSettings,
              })
            }
          }

          /**
           * Processes a Holder URL and extracts configuration from query string
           *
           * @private
           * @param url URL
           * @param instanceOptions Instance options from Holder.run
           */
          function parseURL(url, instanceOptions) {
            var holder = {
              theme: extend(App.settings.themes.gray, null),
              stylesheets: instanceOptions.stylesheets,
              instanceOptions: instanceOptions,
            }

            var firstQuestionMark = url.indexOf('?')
            var parts = [url]

            if (firstQuestionMark !== -1) {
              parts = [
                url.slice(0, firstQuestionMark),
                url.slice(firstQuestionMark + 1),
              ]
            }

            var basics = parts[0].split('/')

            holder.holderURL = url

            var dimensions = basics[1]
            var dimensionData = dimensions.match(/([\d]+p?)x([\d]+p?)/)

            if (!dimensionData) return false

            holder.fluid = dimensions.indexOf('p') !== -1

            holder.dimensions = {
              width: dimensionData[1].replace('p', '%'),
              height: dimensionData[2].replace('p', '%'),
            }

            if (parts.length === 2) {
              var options = querystring.parse(parts[1])

              // Dimensions

              if (utils.truthy(options.ratio)) {
                holder.fluid = true
                var ratioWidth = parseFloat(
                  holder.dimensions.width.replace('%', '')
                )
                var ratioHeight = parseFloat(
                  holder.dimensions.height.replace('%', '')
                )

                ratioHeight = Math.floor(100 * (ratioHeight / ratioWidth))
                ratioWidth = 100

                holder.dimensions.width = ratioWidth + '%'
                holder.dimensions.height = ratioHeight + '%'
              }

              holder.auto = utils.truthy(options.auto)

              // Colors

              if (options.bg) {
                holder.theme.bg = utils.parseColor(options.bg)
              }

              if (options.fg) {
                holder.theme.fg = utils.parseColor(options.fg)
              }

              //todo: add automatic foreground to themes without foreground
              if (options.bg && !options.fg) {
                holder.autoFg = true
              }

              if (
                options.theme &&
                holder.instanceOptions.themes.hasOwnProperty(options.theme)
              ) {
                holder.theme = extend(
                  holder.instanceOptions.themes[options.theme],
                  null
                )
              }

              // Text

              if (options.text) {
                holder.text = options.text
              }

              if (options.textmode) {
                holder.textmode = options.textmode
              }

              if (options.size && parseFloat(options.size)) {
                holder.size = parseFloat(options.size)
              }

              if (options.font) {
                holder.font = options.font
              }

              if (options.align) {
                holder.align = options.align
              }

              if (options.lineWrap) {
                holder.lineWrap = options.lineWrap
              }

              holder.nowrap = utils.truthy(options.nowrap)

              // Miscellaneous

              holder.outline = utils.truthy(options.outline)

              if (utils.truthy(options.random)) {
                App.vars.cache.themeKeys =
                  App.vars.cache.themeKeys ||
                  Object.keys(holder.instanceOptions.themes)
                var _theme =
                  App.vars.cache.themeKeys[
                    0 | (Math.random() * App.vars.cache.themeKeys.length)
                  ]
                holder.theme = extend(
                  holder.instanceOptions.themes[_theme],
                  null
                )
              }
            }

            return holder
          }

          /**
           * Modifies the DOM to fit placeholders and sets up resizable image callbacks (for fluid and automatically sized placeholders)
           *
           * @private
           * @param settings DOM prep settings
           */
          function prepareDOMElement(prepSettings) {
            var mode = prepSettings.mode
            var el = prepSettings.el
            var flags = prepSettings.flags
            var _engineSettings = prepSettings.engineSettings
            var dimensions = flags.dimensions,
              theme = flags.theme
            var dimensionsCaption = dimensions.width + 'x' + dimensions.height
            mode = mode == null ? (flags.fluid ? 'fluid' : 'image') : mode
            var holderTemplateRe = /holder_([a-z]+)/g
            var dimensionsInText = false

            if (flags.text != null) {
              theme.text = flags.text

              //<object> SVG embedding doesn't parse Unicode properly
              if (el.nodeName.toLowerCase() === 'object') {
                var textLines = theme.text.split('\\n')
                for (var k = 0; k < textLines.length; k++) {
                  textLines[k] = utils.encodeHtmlEntity(textLines[k])
                }
                theme.text = textLines.join('\\n')
              }
            }

            if (theme.text) {
              var holderTemplateMatches = theme.text.match(holderTemplateRe)

              if (holderTemplateMatches !== null) {
                //todo: optimize template replacement
                holderTemplateMatches.forEach(function (match) {
                  if (match === 'holder_dimensions') {
                    theme.text = theme.text.replace(match, dimensionsCaption)
                  }
                })
              }
            }

            var holderURL = flags.holderURL
            var engineSettings = extend(_engineSettings, null)

            if (flags.font) {
              /*
              If external fonts are used in a <img> placeholder rendered with SVG, Holder falls back to canvas.
  
              This is done because Firefox and Chrome disallow embedded SVGs from referencing external assets.
              The workaround is either to change the placeholder tag from <img> to <object> or to use the canvas renderer.
              */
              theme.font = flags.font
              if (
                !engineSettings.noFontFallback &&
                el.nodeName.toLowerCase() === 'img' &&
                App.setup.supportsCanvas &&
                engineSettings.renderer === 'svg'
              ) {
                engineSettings = extend(engineSettings, {
                  renderer: 'canvas',
                })
              }
            }

            //Chrome and Opera require a quick 10ms re-render if web fonts are used with canvas
            if (flags.font && engineSettings.renderer == 'canvas') {
              engineSettings.reRender = true
            }

            if (mode == 'background') {
              if (el.getAttribute('data-background-src') == null) {
                DOM.setAttr(el, {
                  'data-background-src': holderURL,
                })
              }
            } else {
              var domProps = {}
              domProps[App.vars.dataAttr] = holderURL
              DOM.setAttr(el, domProps)
            }

            flags.theme = theme

            //todo consider using all renderSettings in holderData
            el.holderData = {
              flags: flags,
              engineSettings: engineSettings,
            }

            if (mode == 'image' || mode == 'fluid') {
              DOM.setAttr(el, {
                alt: theme.text
                  ? dimensionsInText
                    ? theme.text
                    : theme.text + ' [' + dimensionsCaption + ']'
                  : dimensionsCaption,
              })
            }

            var renderSettings = {
              mode: mode,
              el: el,
              holderSettings: {
                dimensions: dimensions,
                theme: theme,
                flags: flags,
              },
              engineSettings: engineSettings,
            }

            if (mode == 'image') {
              if (!flags.auto) {
                el.style.width = dimensions.width + 'px'
                el.style.height = dimensions.height + 'px'
              }

              if (engineSettings.renderer == 'html') {
                el.style.backgroundColor = theme.bg
              } else {
                render(renderSettings)

                if (flags.textmode == 'exact') {
                  el.holderData.resizeUpdate = true
                  App.vars.resizableImages.push(el)
                  updateResizableElements(el)
                }
              }
            } else if (
              mode == 'background' &&
              engineSettings.renderer != 'html'
            ) {
              render(renderSettings)
            } else if (mode == 'fluid') {
              el.holderData.resizeUpdate = true

              if (dimensions.height.slice(-1) == '%') {
                el.style.height = dimensions.height
              } else if (flags.auto == null || !flags.auto) {
                el.style.height = dimensions.height + 'px'
              }
              if (dimensions.width.slice(-1) == '%') {
                el.style.width = dimensions.width
              } else if (flags.auto == null || !flags.auto) {
                el.style.width = dimensions.width + 'px'
              }
              if (
                el.style.display == 'inline' ||
                el.style.display === '' ||
                el.style.display == 'none'
              ) {
                el.style.display = 'block'
              }

              setInitialDimensions(el)

              if (engineSettings.renderer == 'html') {
                el.style.backgroundColor = theme.bg
              } else {
                App.vars.resizableImages.push(el)
                updateResizableElements(el)
              }
            }
          }

          /**
           * Core function that takes output from renderers and sets it as the source or background-image of the target element
           *
           * @private
           * @param renderSettings Renderer settings
           */
          function render(renderSettings) {
            var image = null
            var mode = renderSettings.mode
            var el = renderSettings.el
            var holderSettings = renderSettings.holderSettings
            var engineSettings = renderSettings.engineSettings

            switch (engineSettings.renderer) {
              case 'svg':
                if (!App.setup.supportsSVG) return
                break
              case 'canvas':
                if (!App.setup.supportsCanvas) return
                break
              default:
                return
            }

            //todo: move generation of scene up to flag generation to reduce extra object creation
            var scene = {
              width: holderSettings.dimensions.width,
              height: holderSettings.dimensions.height,
              theme: holderSettings.theme,
              flags: holderSettings.flags,
            }

            var sceneGraph = buildSceneGraph(scene)

            function getRenderedImage() {
              var image = null
              switch (engineSettings.renderer) {
                case 'canvas':
                  image = sgCanvasRenderer(sceneGraph, renderSettings)
                  break
                case 'svg':
                  image = svgRenderer(sceneGraph, renderSettings)
                  break
                default:
                  throw 'Holder: invalid renderer: ' + engineSettings.renderer
              }

              return image
            }

            image = getRenderedImage()

            if (image == null) {
              throw "Holder: couldn't render placeholder"
            }

            //todo: add <object> canvas rendering
            if (mode == 'background') {
              el.style.backgroundImage = 'url(' + image + ')'

              if (!engineSettings.noBackgroundSize) {
                el.style.backgroundSize =
                  scene.width + 'px ' + scene.height + 'px'
              }
            } else {
              if (el.nodeName.toLowerCase() === 'img') {
                DOM.setAttr(el, {
                  src: image,
                })
              } else if (el.nodeName.toLowerCase() === 'object') {
                DOM.setAttr(el, {
                  data: image,
                  type: 'image/svg+xml',
                })
              }
              if (engineSettings.reRender) {
                global.setTimeout(function () {
                  var image = getRenderedImage()
                  if (image == null) {
                    throw "Holder: couldn't render placeholder"
                  }
                  //todo: refactor this code into a function
                  if (el.nodeName.toLowerCase() === 'img') {
                    DOM.setAttr(el, {
                      src: image,
                    })
                  } else if (el.nodeName.toLowerCase() === 'object') {
                    DOM.setAttr(el, {
                      data: image,
                      type: 'image/svg+xml',
                    })
                  }
                }, 150)
              }
            }
            //todo: account for re-rendering
            DOM.setAttr(el, {
              'data-holder-rendered': true,
            })
          }

          /**
           * Core function that takes a Holder scene description and builds a scene graph
           *
           * @private
           * @param scene Holder scene object
           */
          //todo: make this function reusable
          //todo: merge app defaults and setup properties into the scene argument
          function buildSceneGraph(scene) {
            var fontSize = App.defaults.size
            if (parseFloat(scene.theme.size)) {
              fontSize = scene.theme.size
            } else if (parseFloat(scene.flags.size)) {
              fontSize = scene.flags.size
            }

            scene.font = {
              family: scene.theme.font
                ? scene.theme.font
                : 'Arial, Helvetica, Open Sans, sans-serif',
              size: textSize(
                scene.width,
                scene.height,
                fontSize,
                App.defaults.scale
              ),
              units: scene.theme.units ? scene.theme.units : App.defaults.units,
              weight: scene.theme.fontweight ? scene.theme.fontweight : 'bold',
            }

            scene.text =
              scene.theme.text ||
              Math.floor(scene.width) + 'x' + Math.floor(scene.height)

            scene.noWrap = scene.theme.nowrap || scene.flags.nowrap

            scene.align = scene.theme.align || scene.flags.align || 'center'

            switch (scene.flags.textmode) {
              case 'literal':
                scene.text =
                  scene.flags.dimensions.width +
                  'x' +
                  scene.flags.dimensions.height
                break
              case 'exact':
                if (!scene.flags.exactDimensions) break
                scene.text =
                  Math.floor(scene.flags.exactDimensions.width) +
                  'x' +
                  Math.floor(scene.flags.exactDimensions.height)
                break
            }

            var lineWrap = scene.flags.lineWrap || App.setup.lineWrapRatio
            var sceneMargin = scene.width * lineWrap
            var maxLineWidth = sceneMargin

            var sceneGraph = new SceneGraph({
              width: scene.width,
              height: scene.height,
            })

            var Shape = sceneGraph.Shape

            var holderBg = new Shape.Rect('holderBg', {
              fill: scene.theme.bg,
            })

            holderBg.resize(scene.width, scene.height)
            sceneGraph.root.add(holderBg)

            if (scene.flags.outline) {
              var outlineColor = new Color(holderBg.properties.fill)
              outlineColor = outlineColor.lighten(
                outlineColor.lighterThan('7f7f7f') ? -0.1 : 0.1
              )
              holderBg.properties.outline = {
                fill: outlineColor.toHex(true),
                width: 2,
              }
            }

            var holderTextColor = scene.theme.fg

            if (scene.flags.autoFg) {
              var holderBgColor = new Color(holderBg.properties.fill)
              var lightColor = new Color('fff')
              var darkColor = new Color('000', {
                alpha: 0.285714,
              })

              holderTextColor = holderBgColor
                .blendAlpha(
                  holderBgColor.lighterThan('7f7f7f') ? darkColor : lightColor
                )
                .toHex(true)
            }

            var holderTextGroup = new Shape.Group('holderTextGroup', {
              text: scene.text,
              align: scene.align,
              font: scene.font,
              fill: holderTextColor,
            })

            holderTextGroup.moveTo(null, null, 1)
            sceneGraph.root.add(holderTextGroup)

            var tpdata = (holderTextGroup.textPositionData =
              stagingRenderer(sceneGraph))
            if (!tpdata) {
              throw 'Holder: staging fallback not supported yet.'
            }
            holderTextGroup.properties.leading = tpdata.boundingBox.height

            var textNode = null
            var line = null

            function finalizeLine(parent, line, width, height) {
              line.width = width
              line.height = height
              parent.width = Math.max(parent.width, line.width)
              parent.height += line.height
            }

            if (tpdata.lineCount > 1) {
              var offsetX = 0
              var offsetY = 0
              var lineIndex = 0
              var lineKey
              line = new Shape.Group('line' + lineIndex)

              //Double margin so that left/right-aligned next is not flush with edge of image
              if (scene.align === 'left' || scene.align === 'right') {
                maxLineWidth = scene.width * (1 - (1 - lineWrap) * 2)
              }

              for (var i = 0; i < tpdata.words.length; i++) {
                var word = tpdata.words[i]
                textNode = new Shape.Text(word.text)
                var newline = word.text == '\\n'
                if (
                  !scene.noWrap &&
                  (offsetX + word.width >= maxLineWidth || newline === true)
                ) {
                  finalizeLine(
                    holderTextGroup,
                    line,
                    offsetX,
                    holderTextGroup.properties.leading
                  )
                  holderTextGroup.add(line)
                  offsetX = 0
                  offsetY += holderTextGroup.properties.leading
                  lineIndex += 1
                  line = new Shape.Group('line' + lineIndex)
                  line.y = offsetY
                }
                if (newline === true) {
                  continue
                }
                textNode.moveTo(offsetX, 0)
                offsetX += tpdata.spaceWidth + word.width
                line.add(textNode)
              }

              finalizeLine(
                holderTextGroup,
                line,
                offsetX,
                holderTextGroup.properties.leading
              )
              holderTextGroup.add(line)

              if (scene.align === 'left') {
                holderTextGroup.moveTo(scene.width - sceneMargin, null, null)
              } else if (scene.align === 'right') {
                for (lineKey in holderTextGroup.children) {
                  line = holderTextGroup.children[lineKey]
                  line.moveTo(scene.width - line.width, null, null)
                }

                holderTextGroup.moveTo(
                  0 - (scene.width - sceneMargin),
                  null,
                  null
                )
              } else {
                for (lineKey in holderTextGroup.children) {
                  line = holderTextGroup.children[lineKey]
                  line.moveTo(
                    (holderTextGroup.width - line.width) / 2,
                    null,
                    null
                  )
                }

                holderTextGroup.moveTo(
                  (scene.width - holderTextGroup.width) / 2,
                  null,
                  null
                )
              }

              holderTextGroup.moveTo(
                null,
                (scene.height - holderTextGroup.height) / 2,
                null
              )

              //If the text exceeds vertical space, move it down so the first line is visible
              if ((scene.height - holderTextGroup.height) / 2 < 0) {
                holderTextGroup.moveTo(null, 0, null)
              }
            } else {
              textNode = new Shape.Text(scene.text)
              line = new Shape.Group('line0')
              line.add(textNode)
              holderTextGroup.add(line)

              if (scene.align === 'left') {
                holderTextGroup.moveTo(scene.width - sceneMargin, null, null)
              } else if (scene.align === 'right') {
                holderTextGroup.moveTo(
                  0 - (scene.width - sceneMargin),
                  null,
                  null
                )
              } else {
                holderTextGroup.moveTo(
                  (scene.width - tpdata.boundingBox.width) / 2,
                  null,
                  null
                )
              }

              holderTextGroup.moveTo(
                null,
                (scene.height - tpdata.boundingBox.height) / 2,
                null
              )
            }

            //todo: renderlist
            return sceneGraph
          }

          /**
           * Adaptive text sizing function
           *
           * @private
           * @param width Parent width
           * @param height Parent height
           * @param fontSize Requested text size
           * @param scale Proportional scale of text
           */
          function textSize(width, height, fontSize, scale) {
            var stageWidth = parseInt(width, 10)
            var stageHeight = parseInt(height, 10)

            var bigSide = Math.max(stageWidth, stageHeight)
            var smallSide = Math.min(stageWidth, stageHeight)

            var newHeight = 0.8 * Math.min(smallSide, bigSide * scale)
            return Math.round(Math.max(fontSize, newHeight))
          }

          /**
           * Iterates over resizable (fluid or auto) placeholders and renders them
           *
           * @private
           * @param element Optional element selector, specified only if a specific element needs to be re-rendered
           */
          function updateResizableElements(element) {
            var images
            if (element == null || element.nodeType == null) {
              images = App.vars.resizableImages
            } else {
              images = [element]
            }
            for (var i = 0, l = images.length; i < l; i++) {
              var el = images[i]
              if (el.holderData) {
                var flags = el.holderData.flags
                var dimensions = dimensionCheck(el)
                if (dimensions) {
                  if (!el.holderData.resizeUpdate) {
                    continue
                  }

                  if (flags.fluid && flags.auto) {
                    var fluidConfig = el.holderData.fluidConfig
                    switch (fluidConfig.mode) {
                      case 'width':
                        dimensions.height = dimensions.width / fluidConfig.ratio
                        break
                      case 'height':
                        dimensions.width = dimensions.height * fluidConfig.ratio
                        break
                    }
                  }

                  var settings = {
                    mode: 'image',
                    holderSettings: {
                      dimensions: dimensions,
                      theme: flags.theme,
                      flags: flags,
                    },
                    el: el,
                    engineSettings: el.holderData.engineSettings,
                  }

                  if (flags.textmode == 'exact') {
                    flags.exactDimensions = dimensions
                    settings.holderSettings.dimensions = flags.dimensions
                  }

                  render(settings)
                } else {
                  setInvisible(el)
                }
              }
            }
          }

          /**
           * Sets up aspect ratio metadata for fluid placeholders, in order to preserve proportions when resizing
           *
           * @private
           * @param el Image DOM element
           */
          function setInitialDimensions(el) {
            if (el.holderData) {
              var dimensions = dimensionCheck(el)
              if (dimensions) {
                var flags = el.holderData.flags

                var fluidConfig = {
                  fluidHeight: flags.dimensions.height.slice(-1) == '%',
                  fluidWidth: flags.dimensions.width.slice(-1) == '%',
                  mode: null,
                  initialDimensions: dimensions,
                }

                if (fluidConfig.fluidWidth && !fluidConfig.fluidHeight) {
                  fluidConfig.mode = 'width'
                  fluidConfig.ratio =
                    fluidConfig.initialDimensions.width /
                    parseFloat(flags.dimensions.height)
                } else if (!fluidConfig.fluidWidth && fluidConfig.fluidHeight) {
                  fluidConfig.mode = 'height'
                  fluidConfig.ratio =
                    parseFloat(flags.dimensions.width) /
                    fluidConfig.initialDimensions.height
                }

                el.holderData.fluidConfig = fluidConfig
              } else {
                setInvisible(el)
              }
            }
          }

          /**
           * Iterates through all current invisible images, and if they're visible, renders them and removes them from further checks. Runs every animation frame.
           *
           * @private
           */
          function visibilityCheck() {
            var renderableImages = []
            var keys = Object.keys(App.vars.invisibleImages)
            var el

            keys.forEach(function (key) {
              el = App.vars.invisibleImages[key]
              if (dimensionCheck(el) && el.nodeName.toLowerCase() == 'img') {
                renderableImages.push(el)
                delete App.vars.invisibleImages[key]
              }
            })

            if (renderableImages.length) {
              Holder.run({
                images: renderableImages,
              })
            }

            // Done to prevent 100% CPU usage via aggressive calling of requestAnimationFrame
            setTimeout(function () {
              global.requestAnimationFrame(visibilityCheck)
            }, 10)
          }

          /**
           * Starts checking for invisible placeholders if not doing so yet. Does nothing otherwise.
           *
           * @private
           */
          function startVisibilityCheck() {
            if (!App.vars.visibilityCheckStarted) {
              global.requestAnimationFrame(visibilityCheck)
              App.vars.visibilityCheckStarted = true
            }
          }

          /**
           * Sets a unique ID for an image detected to be invisible and adds it to the map of invisible images checked by visibilityCheck
           *
           * @private
           * @param el Invisible DOM element
           */
          function setInvisible(el) {
            if (!el.holderData.invisibleId) {
              App.vars.invisibleId += 1
              App.vars.invisibleImages['i' + App.vars.invisibleId] = el
              el.holderData.invisibleId = App.vars.invisibleId
            }
          }

          //todo: see if possible to convert stagingRenderer to use HTML only
          var stagingRenderer = (function () {
            var svg = null,
              stagingText = null,
              stagingTextNode = null
            return function (graph) {
              var rootNode = graph.root
              if (App.setup.supportsSVG) {
                var firstTimeSetup = false
                var tnode = function (text) {
                  return document.createTextNode(text)
                }
                if (svg == null || svg.parentNode !== document.body) {
                  firstTimeSetup = true
                }

                svg = SVG.initSVG(
                  svg,
                  rootNode.properties.width,
                  rootNode.properties.height
                )
                //Show staging element before staging
                svg.style.display = 'block'

                if (firstTimeSetup) {
                  stagingText = DOM.newEl('text', SVG_NS)
                  stagingTextNode = tnode(null)
                  DOM.setAttr(stagingText, {
                    x: 0,
                  })
                  stagingText.appendChild(stagingTextNode)
                  svg.appendChild(stagingText)
                  document.body.appendChild(svg)
                  svg.style.visibility = 'hidden'
                  svg.style.position = 'absolute'
                  svg.style.top = '-100%'
                  svg.style.left = '-100%'
                  //todo: workaround for zero-dimension <svg> tag in Opera 12
                  //svg.setAttribute('width', 0);
                  //svg.setAttribute('height', 0);
                }

                var holderTextGroup = rootNode.children.holderTextGroup
                var htgProps = holderTextGroup.properties
                DOM.setAttr(stagingText, {
                  y: htgProps.font.size,
                  style: utils.cssProps({
                    'font-weight': htgProps.font.weight,
                    'font-size': htgProps.font.size + htgProps.font.units,
                    'font-family': htgProps.font.family,
                  }),
                })

                //Unescape HTML entities to get approximately the right width
                var txt = DOM.newEl('textarea')
                txt.innerHTML = htgProps.text
                stagingTextNode.nodeValue = txt.value

                //Get bounding box for the whole string (total width and height)
                var stagingTextBBox = stagingText.getBBox()

                //Get line count and split the string into words
                var lineCount = Math.ceil(
                  stagingTextBBox.width / rootNode.properties.width
                )
                var words = htgProps.text.split(' ')
                var newlines = htgProps.text.match(/\\n/g)
                lineCount += newlines == null ? 0 : newlines.length

                //Get bounding box for the string with spaces removed
                stagingTextNode.nodeValue = htgProps.text.replace(/[ ]+/g, '')
                var computedNoSpaceLength = stagingText.getComputedTextLength()

                //Compute average space width
                var diffLength = stagingTextBBox.width - computedNoSpaceLength
                var spaceWidth = Math.round(
                  diffLength / Math.max(1, words.length - 1)
                )

                //Get widths for every word with space only if there is more than one line
                var wordWidths = []
                if (lineCount > 1) {
                  stagingTextNode.nodeValue = ''
                  for (var i = 0; i < words.length; i++) {
                    if (words[i].length === 0) continue
                    stagingTextNode.nodeValue = utils.decodeHtmlEntity(words[i])
                    var bbox = stagingText.getBBox()
                    wordWidths.push({
                      text: words[i],
                      width: bbox.width,
                    })
                  }
                }

                //Hide staging element after staging
                svg.style.display = 'none'

                return {
                  spaceWidth: spaceWidth,
                  lineCount: lineCount,
                  boundingBox: stagingTextBBox,
                  words: wordWidths,
                }
              } else {
                //todo: canvas fallback for measuring text on android 2.3
                return false
              }
            }
          })()

          //Helpers

          /**
           * Prevents a function from being called too often, waits until a timer elapses to call it again
           *
           * @param fn Function to call
           */
          function debounce(fn) {
            if (!App.vars.debounceTimer) fn.call(this)
            if (App.vars.debounceTimer)
              global.clearTimeout(App.vars.debounceTimer)
            App.vars.debounceTimer = global.setTimeout(function () {
              App.vars.debounceTimer = null
              fn.call(this)
            }, App.setup.debounce)
          }

          /**
           * Holder-specific resize/orientation change callback, debounced to prevent excessive execution
           */
          function resizeEvent() {
            debounce(function () {
              updateResizableElements(null)
            })
          }

          //Set up flags

          for (var flag in App.flags) {
            if (!App.flags.hasOwnProperty(flag)) continue
            App.flags[flag].match = function (val) {
              return val.match(this.regex)
            }
          }

          //Properties set once on setup

          App.setup = {
            renderer: 'html',
            debounce: 100,
            ratio: 1,
            supportsCanvas: false,
            supportsSVG: false,
            lineWrapRatio: 0.9,
            dataAttr: 'data-src',
            renderers: ['html', 'canvas', 'svg'],
          }

          //Properties modified during runtime

          App.vars = {
            preempted: false,
            resizableImages: [],
            invisibleImages: {},
            invisibleId: 0,
            visibilityCheckStarted: false,
            debounceTimer: null,
            cache: {},
          }

          //Pre-flight

          ;(function () {
            var canvas = DOM.newEl('canvas')

            if (canvas.getContext) {
              if (
                canvas.toDataURL('image/png').indexOf('data:image/png') != -1
              ) {
                App.setup.renderer = 'canvas'
                App.setup.supportsCanvas = true
              }
            }

            if (
              !!document.createElementNS &&
              !!document.createElementNS(SVG_NS, 'svg').createSVGRect
            ) {
              App.setup.renderer = 'svg'
              App.setup.supportsSVG = true
            }
          })()

          //Starts checking for invisible placeholders
          startVisibilityCheck()

          if (onDomReady) {
            onDomReady(function () {
              if (!App.vars.preempted) {
                Holder.run()
              }
              if (global.addEventListener) {
                global.addEventListener('resize', resizeEvent, false)
                global.addEventListener('orientationchange', resizeEvent, false)
              } else {
                global.attachEvent('onresize', resizeEvent)
              }

              if (typeof global.Turbolinks == 'object') {
                global.document.addEventListener('page:change', function () {
                  Holder.run()
                })
              }
            })
          }

          module.exports = Holder

          /* WEBPACK VAR INJECTION */
        }.call(
          exports,
          (function () {
            return this
          })()
        ))

        /***/
      },
      /* 2 */
      /***/ function (module, exports) {
        /*!
         * onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
         *
         * Specially modified to work with Holder.js
         */

        function _onDomReady(win) {
          //Lazy loading fix for Firefox < 3.6
          //http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
          if (document.readyState == null && document.addEventListener) {
            document.addEventListener(
              'DOMContentLoaded',
              function DOMContentLoaded() {
                document.removeEventListener(
                  'DOMContentLoaded',
                  DOMContentLoaded,
                  false
                )
                document.readyState = 'complete'
              },
              false
            )
            document.readyState = 'loading'
          }

          var doc = win.document,
            docElem = doc.documentElement,
            LOAD = 'load',
            FALSE = false,
            ONLOAD = 'on' + LOAD,
            COMPLETE = 'complete',
            READYSTATE = 'readyState',
            ATTACHEVENT = 'attachEvent',
            DETACHEVENT = 'detachEvent',
            ADDEVENTLISTENER = 'addEventListener',
            DOMCONTENTLOADED = 'DOMContentLoaded',
            ONREADYSTATECHANGE = 'onreadystatechange',
            REMOVEEVENTLISTENER = 'removeEventListener',
            // W3C Event model
            w3c = ADDEVENTLISTENER in doc,
            _top = FALSE,
            // isReady: Is the DOM ready to be used? Set to true once it occurs.
            isReady = FALSE,
            // Callbacks pending execution until DOM is ready
            callbacks = []

          // Handle when the DOM is ready
          function ready(fn) {
            if (!isReady) {
              // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
              if (!doc.body) {
                return defer(ready)
              }

              // Remember that the DOM is ready
              isReady = true

              // Execute all callbacks
              while ((fn = callbacks.shift())) {
                defer(fn)
              }
            }
          }

          // The ready event handler
          function completed(event) {
            // readyState === "complete" is good enough for us to call the dom ready in oldIE
            if (w3c || event.type === LOAD || doc[READYSTATE] === COMPLETE) {
              detach()
              ready()
            }
          }

          // Clean-up method for dom ready events
          function detach() {
            if (w3c) {
              doc[REMOVEEVENTLISTENER](DOMCONTENTLOADED, completed, FALSE)
              win[REMOVEEVENTLISTENER](LOAD, completed, FALSE)
            } else {
              doc[DETACHEVENT](ONREADYSTATECHANGE, completed)
              win[DETACHEVENT](ONLOAD, completed)
            }
          }

          // Defers a function, scheduling it to run after the current call stack has cleared.
          function defer(fn, wait) {
            // Allow 0 to be passed
            setTimeout(fn, +wait >= 0 ? wait : 1)
          }

          // Attach the listeners:

          // Catch cases where onDomReady is called after the browser event has already occurred.
          // we once tried to use readyState "interactive" here, but it caused issues like the one
          // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
          if (doc[READYSTATE] === COMPLETE) {
            // Handle it asynchronously to allow scripts the opportunity to delay ready
            defer(ready)

            // Standards-based browsers support DOMContentLoaded
          } else if (w3c) {
            // Use the handy event callback
            doc[ADDEVENTLISTENER](DOMCONTENTLOADED, completed, FALSE)

            // A fallback to window.onload, that will always work
            win[ADDEVENTLISTENER](LOAD, completed, FALSE)

            // If IE event model is used
          } else {
            // Ensure firing before onload, maybe late but safe also for iframes
            doc[ATTACHEVENT](ONREADYSTATECHANGE, completed)

            // A fallback to window.onload, that will always work
            win[ATTACHEVENT](ONLOAD, completed)

            // If IE and not a frame
            // continually check to see if the document is ready
            try {
              _top = win.frameElement == null && docElem
            } catch (e) {}

            if (_top && _top.doScroll) {
              ;(function doScrollCheck() {
                if (!isReady) {
                  try {
                    // Use the trick by Diego Perini
                    // http://javascript.nwbox.com/IEContentLoaded/
                    _top.doScroll('left')
                  } catch (e) {
                    return defer(doScrollCheck, 50)
                  }

                  // detach all dom ready events
                  detach()

                  // and execute any waiting functions
                  ready()
                }
              })()
            }
          }

          function onDomReady(fn) {
            // If DOM is ready, execute the function (async), otherwise wait
            isReady ? defer(fn) : callbacks.push(fn)
          }

          // Add version
          onDomReady.version = '1.4.0'
          // Add method to check if DOM is ready
          onDomReady.isReady = function () {
            return isReady
          }

          return onDomReady
        }

        module.exports = typeof window !== 'undefined' && _onDomReady(window)

        /***/
      },
      /* 3 */
      /***/ function (module, exports, __webpack_require__) {
        //Modified version of component/querystring
        //Changes: updated dependencies, dot notation parsing, JSHint fixes
        //Fork at https://github.com/imsky/querystring

        /**
         * Module dependencies.
         */

        var encode = encodeURIComponent
        var decode = decodeURIComponent
        var trim = __webpack_require__(4)
        var type = __webpack_require__(5)

        var arrayRegex = /(\w+)\[(\d+)\]/
        var objectRegex = /\w+\.\w+/

        /**
         * Parse the given query `str`.
         *
         * @param {String} str
         * @return {Object}
         * @api public
         */

        exports.parse = function (str) {
          if ('string' !== typeof str) return {}

          str = trim(str)
          if ('' === str) return {}
          if ('?' === str.charAt(0)) str = str.slice(1)

          var obj = {}
          var pairs = str.split('&')
          for (var i = 0; i < pairs.length; i++) {
            var parts = pairs[i].split('=')
            var key = decode(parts[0])
            var m, ctx, prop

            if ((m = arrayRegex.exec(key))) {
              obj[m[1]] = obj[m[1]] || []
              obj[m[1]][m[2]] = decode(parts[1])
              continue
            }

            if ((m = objectRegex.test(key))) {
              m = key.split('.')
              ctx = obj

              while (m.length) {
                prop = m.shift()

                if (!prop.length) continue

                if (!ctx[prop]) {
                  ctx[prop] = {}
                } else if (ctx[prop] && typeof ctx[prop] !== 'object') {
                  break
                }

                if (!m.length) {
                  ctx[prop] = decode(parts[1])
                }

                ctx = ctx[prop]
              }

              continue
            }

            obj[parts[0]] = null == parts[1] ? '' : decode(parts[1])
          }

          return obj
        }

        /**
         * Stringify the given `obj`.
         *
         * @param {Object} obj
         * @return {String}
         * @api public
         */

        exports.stringify = function (obj) {
          if (!obj) return ''
          var pairs = []

          for (var key in obj) {
            var value = obj[key]

            if ('array' == type(value)) {
              for (var i = 0; i < value.length; ++i) {
                pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]))
              }
              continue
            }

            pairs.push(encode(key) + '=' + encode(obj[key]))
          }

          return pairs.join('&')
        }

        /***/
      },
      /* 4 */
      /***/ function (module, exports) {
        exports = module.exports = trim

        function trim(str) {
          return str.replace(/^\s*|\s*$/g, '')
        }

        exports.left = function (str) {
          return str.replace(/^\s*/, '')
        }

        exports.right = function (str) {
          return str.replace(/\s*$/, '')
        }

        /***/
      },
      /* 5 */
      /***/ function (module, exports) {
        /**
         * toString ref.
         */

        var toString = Object.prototype.toString

        /**
         * Return the type of `val`.
         *
         * @param {Mixed} val
         * @return {String}
         * @api public
         */

        module.exports = function (val) {
          switch (toString.call(val)) {
            case '[object Date]':
              return 'date'
            case '[object RegExp]':
              return 'regexp'
            case '[object Arguments]':
              return 'arguments'
            case '[object Array]':
              return 'array'
            case '[object Error]':
              return 'error'
          }

          if (val === null) return 'null'
          if (val === undefined) return 'undefined'
          if (val !== val) return 'nan'
          if (val && val.nodeType === 1) return 'element'

          val = val.valueOf
            ? val.valueOf()
            : Object.prototype.valueOf.apply(val)

          return typeof val
        }

        /***/
      },
      /* 6 */
      /***/ function (module, exports) {
        var SceneGraph = function (sceneProperties) {
          var nodeCount = 1

          //todo: move merge to helpers section
          function merge(parent, child) {
            for (var prop in child) {
              parent[prop] = child[prop]
            }
            return parent
          }

          var SceneNode = function (name) {
            nodeCount++
            this.parent = null
            this.children = {}
            this.id = nodeCount
            this.name = 'n' + nodeCount
            if (typeof name !== 'undefined') {
              this.name = name
            }
            this.x = this.y = this.z = 0
            this.width = this.height = 0
          }

          SceneNode.prototype.resize = function (width, height) {
            if (width != null) {
              this.width = width
            }
            if (height != null) {
              this.height = height
            }
          }

          SceneNode.prototype.moveTo = function (x, y, z) {
            this.x = x != null ? x : this.x
            this.y = y != null ? y : this.y
            this.z = z != null ? z : this.z
          }

          SceneNode.prototype.add = function (child) {
            var name = child.name
            if (typeof this.children[name] === 'undefined') {
              this.children[name] = child
              child.parent = this
            } else {
              throw 'SceneGraph: child already exists: ' + name
            }
          }

          var RootNode = function () {
            SceneNode.call(this, 'root')
            this.properties = sceneProperties
          }

          RootNode.prototype = new SceneNode()

          var Shape = function (name, props) {
            SceneNode.call(this, name)
            this.properties = {
              fill: '#000000',
            }
            if (typeof props !== 'undefined') {
              merge(this.properties, props)
            } else if (
              typeof name !== 'undefined' &&
              typeof name !== 'string'
            ) {
              throw 'SceneGraph: invalid node name'
            }
          }

          Shape.prototype = new SceneNode()

          var Group = function () {
            Shape.apply(this, arguments)
            this.type = 'group'
          }

          Group.prototype = new Shape()

          var Rect = function () {
            Shape.apply(this, arguments)
            this.type = 'rect'
          }

          Rect.prototype = new Shape()

          var Text = function (text) {
            Shape.call(this)
            this.type = 'text'
            this.properties.text = text
          }

          Text.prototype = new Shape()

          var root = new RootNode()

          this.Shape = {
            Rect: Rect,
            Text: Text,
            Group: Group,
          }

          this.root = root
          return this
        }

        module.exports = SceneGraph

        /***/
      },
      /* 7 */
      /***/ function (module, exports) {
        /* WEBPACK VAR INJECTION */ ;(function (global) {
          /**
           * Shallow object clone and merge
           *
           * @param a Object A
           * @param b Object B
           * @returns {Object} New object with all of A's properties, and all of B's properties, overwriting A's properties
           */
          exports.extend = function (a, b) {
            var c = {}
            for (var x in a) {
              if (a.hasOwnProperty(x)) {
                c[x] = a[x]
              }
            }
            if (b != null) {
              for (var y in b) {
                if (b.hasOwnProperty(y)) {
                  c[y] = b[y]
                }
              }
            }
            return c
          }

          /**
           * Takes a k/v list of CSS properties and returns a rule
           *
           * @param props CSS properties object
           */
          exports.cssProps = function (props) {
            var ret = []
            for (var p in props) {
              if (props.hasOwnProperty(p)) {
                ret.push(p + ':' + props[p])
              }
            }
            return ret.join(';')
          }

          /**
           * Encodes HTML entities in a string
           *
           * @param str Input string
           */
          exports.encodeHtmlEntity = function (str) {
            var buf = []
            var charCode = 0
            for (var i = str.length - 1; i >= 0; i--) {
              charCode = str.charCodeAt(i)
              if (charCode > 128) {
                buf.unshift(['&#', charCode, ';'].join(''))
              } else {
                buf.unshift(str[i])
              }
            }
            return buf.join('')
          }

          /**
           * Checks if an image exists
           *
           * @param src URL of image
           * @param callback Callback to call once image status has been found
           */
          exports.imageExists = function (src, callback) {
            var image = new Image()
            image.onerror = function () {
              callback.call(this, false)
            }
            image.onload = function () {
              callback.call(this, true)
            }
            image.src = src
          }

          /**
           * Decodes HTML entities in a string
           *
           * @param str Input string
           */
          exports.decodeHtmlEntity = function (str) {
            return str.replace(/&#(\d+);/g, function (match, dec) {
              return String.fromCharCode(dec)
            })
          }

          /**
           * Returns an element's dimensions if it's visible, `false` otherwise.
           *
           * @param el DOM element
           */
          exports.dimensionCheck = function (el) {
            var dimensions = {
              height: el.clientHeight,
              width: el.clientWidth,
            }

            if (dimensions.height && dimensions.width) {
              return dimensions
            } else {
              return false
            }
          }

          /**
           * Returns true if value is truthy or if it is "semantically truthy"
           * @param val
           */
          exports.truthy = function (val) {
            if (typeof val === 'string') {
              return (
                val === 'true' ||
                val === 'yes' ||
                val === '1' ||
                val === 'on' ||
                val === '✓'
              )
            }
            return !!val
          }

          /**
           * Parses input into a well-formed CSS color
           * @param val
           */
          exports.parseColor = function (val) {
            var hexre = /(^(?:#?)[0-9a-f]{6}$)|(^(?:#?)[0-9a-f]{3}$)/i
            var rgbre = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/
            var rgbare =
              /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0*\.\d{1,}|1)\)$/

            var match = val.match(hexre)
            var retval

            if (match !== null) {
              retval = match[1] || match[2]
              if (retval[0] !== '#') {
                return '#' + retval
              } else {
                return retval
              }
            }

            match = val.match(rgbre)

            if (match !== null) {
              retval = 'rgb(' + match.slice(1).join(',') + ')'
              return retval
            }

            match = val.match(rgbare)

            if (match !== null) {
              const normalizeAlpha = function (a) {
                return '0.' + a.split('.')[1]
              }
              const fixedMatch = match.slice(1).map(function (e, i) {
                return i === 3 ? normalizeAlpha(e) : e
              })
              retval = 'rgba(' + fixedMatch.join(',') + ')'
              return retval
            }

            return null
          }

          /**
           * Provides the correct scaling ratio for canvas drawing operations on HiDPI screens (e.g. Retina displays)
           */
          exports.canvasRatio = function () {
            var devicePixelRatio = 1
            var backingStoreRatio = 1

            if (global.document) {
              var canvas = global.document.createElement('canvas')
              if (canvas.getContext) {
                var ctx = canvas.getContext('2d')
                devicePixelRatio = global.devicePixelRatio || 1
                backingStoreRatio =
                  ctx.webkitBackingStorePixelRatio ||
                  ctx.mozBackingStorePixelRatio ||
                  ctx.msBackingStorePixelRatio ||
                  ctx.oBackingStorePixelRatio ||
                  ctx.backingStorePixelRatio ||
                  1
              }
            }

            return devicePixelRatio / backingStoreRatio
          }

          /* WEBPACK VAR INJECTION */
        }.call(
          exports,
          (function () {
            return this
          })()
        ))

        /***/
      },
      /* 8 */
      /***/ function (module, exports, __webpack_require__) {
        /* WEBPACK VAR INJECTION */ ;(function (global) {
          var DOM = __webpack_require__(9)

          var SVG_NS = 'http://www.w3.org/2000/svg'
          var NODE_TYPE_COMMENT = 8

          /**
           * Generic SVG element creation function
           *
           * @param svg SVG context, set to null if new
           * @param width Document width
           * @param height Document height
           */
          exports.initSVG = function (svg, width, height) {
            var defs,
              style,
              initialize = false

            if (svg && svg.querySelector) {
              style = svg.querySelector('style')
              if (style === null) {
                initialize = true
              }
            } else {
              svg = DOM.newEl('svg', SVG_NS)
              initialize = true
            }

            if (initialize) {
              defs = DOM.newEl('defs', SVG_NS)
              style = DOM.newEl('style', SVG_NS)
              DOM.setAttr(style, {
                type: 'text/css',
              })
              defs.appendChild(style)
              svg.appendChild(defs)
            }

            //IE throws an exception if this is set and Chrome requires it to be set
            if (svg.webkitMatchesSelector) {
              svg.setAttribute('xmlns', SVG_NS)
            }

            //Remove comment nodes
            for (var i = 0; i < svg.childNodes.length; i++) {
              if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) {
                svg.removeChild(svg.childNodes[i])
              }
            }

            //Remove CSS
            while (style.childNodes.length) {
              style.removeChild(style.childNodes[0])
            }

            DOM.setAttr(svg, {
              width: width,
              height: height,
              viewBox: '0 0 ' + width + ' ' + height,
              preserveAspectRatio: 'none',
            })

            return svg
          }

          /**
           * Converts serialized SVG to a string suitable for data URI use
           * @param svgString Serialized SVG string
           * @param [base64] Use base64 encoding for data URI
           */
          exports.svgStringToDataURI = (function () {
            var rawPrefix = 'data:image/svg+xml;charset=UTF-8,'
            var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,'

            return function (svgString, base64) {
              if (base64) {
                return (
                  base64Prefix +
                  btoa(global.unescape(encodeURIComponent(svgString)))
                )
              } else {
                return rawPrefix + encodeURIComponent(svgString)
              }
            }
          })()

          /**
           * Returns serialized SVG with XML processing instructions
           *
           * @param svg SVG context
           * @param stylesheets CSS stylesheets to include
           */
          exports.serializeSVG = function (svg, engineSettings) {
            if (!global.XMLSerializer) return
            var serializer = new XMLSerializer()
            var svgCSS = ''
            var stylesheets = engineSettings.stylesheets

            //External stylesheets: Processing Instruction method
            if (engineSettings.svgXMLStylesheet) {
              var xml = DOM.createXML()
              //Add <?xml-stylesheet ?> directives
              for (var i = stylesheets.length - 1; i >= 0; i--) {
                var csspi = xml.createProcessingInstruction(
                  'xml-stylesheet',
                  'href="' + stylesheets[i] + '" rel="stylesheet"'
                )
                xml.insertBefore(csspi, xml.firstChild)
              }

              xml.removeChild(xml.documentElement)
              svgCSS = serializer.serializeToString(xml)
            }

            var svgText = serializer.serializeToString(svg)
            svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1')
            return svgCSS + svgText
          }

          /* WEBPACK VAR INJECTION */
        }.call(
          exports,
          (function () {
            return this
          })()
        ))

        /***/
      },
      /* 9 */
      /***/ function (module, exports) {
        /* WEBPACK VAR INJECTION */ ;(function (global) {
          /**
           * Generic new DOM element function
           *
           * @param tag Tag to create
           * @param namespace Optional namespace value
           */
          exports.newEl = function (tag, namespace) {
            if (!global.document) return

            if (namespace == null) {
              return global.document.createElement(tag)
            } else {
              return global.document.createElementNS(namespace, tag)
            }
          }

          /**
           * Generic setAttribute function
           *
           * @param el Reference to DOM element
           * @param attrs Object with attribute keys and values
           */
          exports.setAttr = function (el, attrs) {
            for (var a in attrs) {
              el.setAttribute(a, attrs[a])
            }
          }

          /**
           * Creates a XML document
           * @private
           */
          exports.createXML = function () {
            if (!global.DOMParser) return
            return new DOMParser().parseFromString('<xml />', 'application/xml')
          }

          /**
           * Converts a value into an array of DOM nodes
           *
           * @param val A string, a NodeList, a Node, or an HTMLCollection
           */
          exports.getNodeArray = function (val) {
            var retval = null
            if (typeof val == 'string') {
              retval = document.querySelectorAll(val)
            } else if (global.NodeList && val instanceof global.NodeList) {
              retval = val
            } else if (global.Node && val instanceof global.Node) {
              retval = [val]
            } else if (
              global.HTMLCollection &&
              val instanceof global.HTMLCollection
            ) {
              retval = val
            } else if (val instanceof Array) {
              retval = val
            } else if (val === null) {
              retval = []
            }

            retval = Array.prototype.slice.call(retval)

            return retval
          }

          /* WEBPACK VAR INJECTION */
        }.call(
          exports,
          (function () {
            return this
          })()
        ))

        /***/
      },
      /* 10 */
      /***/ function (module, exports) {
        var Color = function (color, options) {
          //todo: support rgba, hsla, and rrggbbaa notation
          //todo: use CIELAB internally
          //todo: add clamp function (with sign)
          if (typeof color !== 'string') return

          this.original = color

          if (color.charAt(0) === '#') {
            color = color.slice(1)
          }

          if (/[^a-f0-9]+/i.test(color)) return

          if (color.length === 3) {
            color = color.replace(/./g, '$&$&')
          }

          if (color.length !== 6) return

          this.alpha = 1

          if (options && options.alpha) {
            this.alpha = options.alpha
          }

          this.set(parseInt(color, 16))
        }

        //todo: jsdocs
        Color.rgb2hex = function (r, g, b) {
          function format(decimal) {
            var hex = (decimal | 0).toString(16)
            if (decimal < 16) {
              hex = '0' + hex
            }
            return hex
          }

          return [r, g, b].map(format).join('')
        }

        //todo: jsdocs
        Color.hsl2rgb = function (h, s, l) {
          var H = h / 60
          var C = (1 - Math.abs(2 * l - 1)) * s
          var X = C * (1 - Math.abs((parseInt(H) % 2) - 1))
          var m = l - C / 2

          var r = 0,
            g = 0,
            b = 0

          if (H >= 0 && H < 1) {
            r = C
            g = X
          } else if (H >= 1 && H < 2) {
            r = X
            g = C
          } else if (H >= 2 && H < 3) {
            g = C
            b = X
          } else if (H >= 3 && H < 4) {
            g = X
            b = C
          } else if (H >= 4 && H < 5) {
            r = X
            b = C
          } else if (H >= 5 && H < 6) {
            r = C
            b = X
          }

          r += m
          g += m
          b += m

          r = parseInt(r * 255)
          g = parseInt(g * 255)
          b = parseInt(b * 255)

          return [r, g, b]
        }

        /**
         * Sets the color from a raw RGB888 integer
         * @param raw RGB888 representation of color
         */
        //todo: refactor into a static method
        //todo: factor out individual color spaces
        //todo: add HSL, CIELAB, and CIELUV
        Color.prototype.set = function (val) {
          this.raw = val

          var r = (this.raw & 0xff0000) >> 16
          var g = (this.raw & 0x00ff00) >> 8
          var b = this.raw & 0x0000ff

          // BT.709
          var y = 0.2126 * r + 0.7152 * g + 0.0722 * b
          var u = -0.09991 * r - 0.33609 * g + 0.436 * b
          var v = 0.615 * r - 0.55861 * g - 0.05639 * b

          this.rgb = {
            r: r,
            g: g,
            b: b,
          }

          this.yuv = {
            y: y,
            u: u,
            v: v,
          }

          return this
        }

        /**
         * Lighten or darken a color
         * @param multiplier Amount to lighten or darken (-1 to 1)
         */
        Color.prototype.lighten = function (multiplier) {
          var cm =
            Math.min(1, Math.max(0, Math.abs(multiplier))) *
            (multiplier < 0 ? -1 : 1)
          var bm = (255 * cm) | 0
          var cr = Math.min(255, Math.max(0, this.rgb.r + bm))
          var cg = Math.min(255, Math.max(0, this.rgb.g + bm))
          var cb = Math.min(255, Math.max(0, this.rgb.b + bm))
          var hex = Color.rgb2hex(cr, cg, cb)
          return new Color(hex)
        }

        /**
         * Output color in hex format
         * @param addHash Add a hash character to the beginning of the output
         */
        Color.prototype.toHex = function (addHash) {
          return (addHash ? '#' : '') + this.raw.toString(16)
        }

        /**
         * Returns whether or not current color is lighter than another color
         * @param color Color to compare against
         */
        Color.prototype.lighterThan = function (color) {
          if (!(color instanceof Color)) {
            color = new Color(color)
          }

          return this.yuv.y > color.yuv.y
        }

        /**
         * Returns the result of mixing current color with another color
         * @param color Color to mix with
         * @param multiplier How much to mix with the other color
         */
        /*
      Color.prototype.mix = function (color, multiplier) {
          if (!(color instanceof Color)) {
              color = new Color(color);
          }
  
          var r = this.rgb.r;
          var g = this.rgb.g;
          var b = this.rgb.b;
          var a = this.alpha;
  
          var m = typeof multiplier !== 'undefined' ? multiplier : 0.5;
  
          //todo: write a lerp function
          r = r + m * (color.rgb.r - r);
          g = g + m * (color.rgb.g - g);
          b = b + m * (color.rgb.b - b);
          a = a + m * (color.alpha - a);
  
          return new Color(Color.rgbToHex(r, g, b), {
              'alpha': a
          });
      };
      */

        /**
         * Returns the result of blending another color on top of current color with alpha
         * @param color Color to blend on top of current color, i.e. "Ca"
         */
        //todo: see if .blendAlpha can be merged into .mix
        Color.prototype.blendAlpha = function (color) {
          if (!(color instanceof Color)) {
            color = new Color(color)
          }

          var Ca = color
          var Cb = this

          //todo: write alpha blending function
          var r = Ca.alpha * Ca.rgb.r + (1 - Ca.alpha) * Cb.rgb.r
          var g = Ca.alpha * Ca.rgb.g + (1 - Ca.alpha) * Cb.rgb.g
          var b = Ca.alpha * Ca.rgb.b + (1 - Ca.alpha) * Cb.rgb.b

          return new Color(Color.rgb2hex(r, g, b))
        }

        module.exports = Color

        /***/
      },
      /* 11 */
      /***/ function (module, exports) {
        module.exports = {
          version: '2.9.7',
          svg_ns: 'http://www.w3.org/2000/svg',
        }

        /***/
      },
      /* 12 */
      /***/ function (module, exports, __webpack_require__) {
        var shaven = __webpack_require__(13)

        var SVG = __webpack_require__(8)
        var constants = __webpack_require__(11)
        var utils = __webpack_require__(7)

        var SVG_NS = constants.svg_ns

        var templates = {
          element: function (options) {
            var tag = options.tag
            var content = options.content || ''
            delete options.tag
            delete options.content
            return [tag, content, options]
          },
        }

        //todo: deprecate tag arg, infer tag from shape object
        function convertShape(shape, tag) {
          return templates.element({
            tag: tag,
            width: shape.width,
            height: shape.height,
            fill: shape.properties.fill,
          })
        }

        function textCss(properties) {
          return utils.cssProps({
            fill: properties.fill,
            'font-weight': properties.font.weight,
            'font-family': properties.font.family + ', monospace',
            'font-size': properties.font.size + properties.font.units,
          })
        }

        function outlinePath(bgWidth, bgHeight, outlineWidth) {
          var outlineOffsetWidth = outlineWidth / 2

          return [
            'M',
            outlineOffsetWidth,
            outlineOffsetWidth,
            'H',
            bgWidth - outlineOffsetWidth,
            'V',
            bgHeight - outlineOffsetWidth,
            'H',
            outlineOffsetWidth,
            'V',
            0,
            'M',
            0,
            outlineOffsetWidth,
            'L',
            bgWidth,
            bgHeight - outlineOffsetWidth,
            'M',
            0,
            bgHeight - outlineOffsetWidth,
            'L',
            bgWidth,
            outlineOffsetWidth,
          ].join(' ')
        }

        module.exports = function (sceneGraph, renderSettings) {
          var engineSettings = renderSettings.engineSettings
          var stylesheets = engineSettings.stylesheets
          var stylesheetXml = stylesheets
            .map(function (stylesheet) {
              return (
                '<?xml-stylesheet rel="stylesheet" href="' + stylesheet + '"?>'
              )
            })
            .join('\n')

          var holderId = 'holder_' + Number(new Date()).toString(16)

          var root = sceneGraph.root
          var textGroup = root.children.holderTextGroup

          var css =
            '#' + holderId + ' text { ' + textCss(textGroup.properties) + ' } '

          // push text down to be equally vertically aligned with canvas renderer
          textGroup.y += textGroup.textPositionData.boundingBox.height * 0.8

          var wordTags = []

          Object.keys(textGroup.children).forEach(function (lineKey) {
            var line = textGroup.children[lineKey]

            Object.keys(line.children).forEach(function (wordKey) {
              var word = line.children[wordKey]
              var x = textGroup.x + line.x + word.x
              var y = textGroup.y + line.y + word.y
              var wordTag = templates.element({
                tag: 'text',
                content: word.properties.text,
                x: x,
                y: y,
              })

              wordTags.push(wordTag)
            })
          })

          var text = templates.element({
            tag: 'g',
            content: wordTags,
          })

          var outline = null

          if (root.children.holderBg.properties.outline) {
            var outlineProperties = root.children.holderBg.properties.outline
            outline = templates.element({
              tag: 'path',
              d: outlinePath(
                root.children.holderBg.width,
                root.children.holderBg.height,
                outlineProperties.width
              ),
              'stroke-width': outlineProperties.width,
              stroke: outlineProperties.fill,
              fill: 'none',
            })
          }

          var bg = convertShape(root.children.holderBg, 'rect')

          var sceneContent = []

          sceneContent.push(bg)
          if (outlineProperties) {
            sceneContent.push(outline)
          }
          sceneContent.push(text)

          var scene = templates.element({
            tag: 'g',
            id: holderId,
            content: sceneContent,
          })

          var style = templates.element({
            tag: 'style',
            //todo: figure out how to add CDATA directive
            content: css,
            type: 'text/css',
          })

          var defs = templates.element({
            tag: 'defs',
            content: style,
          })

          var svg = templates.element({
            tag: 'svg',
            content: [defs, scene],
            width: root.properties.width,
            height: root.properties.height,
            xmlns: SVG_NS,
            viewBox: [0, 0, root.properties.width, root.properties.height].join(
              ' '
            ),
            preserveAspectRatio: 'none',
          })

          var output = shaven(svg)

          if (/\&amp;(x)?#[0-9A-Fa-f]/.test(output[0])) {
            output[0] = output[0].replace(/&amp;#/gm, '&#')
          }

          output = stylesheetXml + output[0]

          var svgString = SVG.svgStringToDataURI(
            output,
            renderSettings.mode === 'background'
          )
          return svgString
        }

        /***/
      },
      /* 13 */
      /***/ function (module, exports, __webpack_require__) {
        var escape = __webpack_require__(14)

        // TODO: remove namespace

        module.exports = function shaven(array, namespace, returnObject) {
          'use strict'

          var i = 1
          var doesEscape = true
          var HTMLString
          var attributeKey
          var callback
          var key

          returnObject = returnObject || {}

          function createElement(sugarString) {
            var tags = sugarString.match(/^[\w-]+/)
            var element = {
              tag: tags ? tags[0] : 'div',
              attr: {},
              children: [],
            }
            var id = sugarString.match(/#([\w-]+)/)
            var reference = sugarString.match(/\$([\w-]+)/)
            var classNames = sugarString.match(/\.[\w-]+/g)

            // Assign id if is set
            if (id) {
              element.attr.id = id[1]

              // Add element to the return object
              returnObject[id[1]] = element
            }

            if (reference) returnObject[reference[1]] = element

            if (classNames)
              element.attr.class = classNames.join(' ').replace(/\./g, '')

            if (sugarString.match(/&$/g)) doesEscape = false

            return element
          }

          function replacer(key, value) {
            if (value === null || value === false || value === undefined) return

            if (typeof value !== 'string' && typeof value !== 'object')
              return String(value)

            return value
          }

          function escapeAttribute(string) {
            return string || string === 0
              ? String(string).replace(/&/g, '&amp;').replace(/"/g, '&quot;')
              : ''
          }

          function escapeHTML(string) {
            return String(string)
              .replace(/&/g, '&amp;')
              .replace(/"/g, '&quot;')
              .replace(/'/g, '&apos;')
              .replace(/</g, '&lt;')
              .replace(/>/g, '&gt;')
          }

          if (typeof array[0] === 'string') array[0] = createElement(array[0])
          else if (Array.isArray(array[0])) i = 0
          else
            throw new Error(
              'First element of array must be a string, ' +
                'or an array and not ' +
                JSON.stringify(array[0])
            )

          for (; i < array.length; i++) {
            // Don't render element if value is false or null
            if (array[i] === false || array[i] === null) {
              array[0] = false
              break
            }

            // Continue with next array value if current value is undefined or true
            else if (array[i] === undefined || array[i] === true) {
              continue
            } else if (typeof array[i] === 'string') {
              if (doesEscape) array[i] = escapeHTML(array[i])

              array[0].children.push(array[i])
            } else if (typeof array[i] === 'number') {
              array[0].children.push(array[i])
            } else if (Array.isArray(array[i])) {
              if (Array.isArray(array[i][0])) {
                array[i].reverse().forEach(function (subArray) {
                  array.splice(i + 1, 0, subArray)
                })

                if (i !== 0) continue
                i++
              }

              shaven(array[i], namespace, returnObject)

              if (array[i][0]) array[0].children.push(array[i][0])
            } else if (typeof array[i] === 'function') callback = array[i]
            else if (typeof array[i] === 'object') {
              for (attributeKey in array[i])
                if (array[i].hasOwnProperty(attributeKey))
                  if (
                    array[i][attributeKey] !== null &&
                    array[i][attributeKey] !== false
                  )
                    if (
                      attributeKey === 'style' &&
                      typeof array[i][attributeKey] === 'object'
                    )
                      array[0].attr[attributeKey] = JSON.stringify(
                        array[i][attributeKey],
                        replacer
                      )
                        .slice(2, -2)
                        .replace(/","/g, ';')
                        .replace(/":"/g, ':')
                        .replace(/\\"/g, "'")
                    else array[0].attr[attributeKey] = array[i][attributeKey]
            } else
              throw new TypeError(
                '"' + array[i] + '" is not allowed as a value.'
              )
          }

          if (array[0] !== false) {
            HTMLString = '<' + array[0].tag

            for (key in array[0].attr)
              if (array[0].attr.hasOwnProperty(key))
                HTMLString +=
                  ' ' + key + '="' + escapeAttribute(array[0].attr[key]) + '"'

            HTMLString += '>'

            array[0].children.forEach(function (child) {
              HTMLString += child
            })

            HTMLString += '</' + array[0].tag + '>'

            array[0] = HTMLString
          }

          // Return root element on index 0
          returnObject[0] = array[0]

          if (callback) callback(array[0])

          // returns object containing all elements with an id and the root element
          return returnObject
        }

        /***/
      },
      /* 14 */
      /***/ function (module, exports) {
        /*!
         * escape-html
         * Copyright(c) 2012-2013 TJ Holowaychuk
         * Copyright(c) 2015 Andreas Lubbe
         * Copyright(c) 2015 Tiancheng "Timothy" Gu
         * MIT Licensed
         */

        'use strict'

        /**
         * Module variables.
         * @private
         */

        var matchHtmlRegExp = /["'&<>]/

        /**
         * Module exports.
         * @public
         */

        module.exports = escapeHtml

        /**
         * Escape special characters in the given string of html.
         *
         * @param  {string} string The string to escape for inserting into HTML
         * @return {string}
         * @public
         */

        function escapeHtml(string) {
          var str = '' + string
          var match = matchHtmlRegExp.exec(str)

          if (!match) {
            return str
          }

          var escape
          var html = ''
          var index = 0
          var lastIndex = 0

          for (index = match.index; index < str.length; index++) {
            switch (str.charCodeAt(index)) {
              case 34: // "
                escape = '&quot;'
                break
              case 38: // &
                escape = '&amp;'
                break
              case 39: // '
                escape = '&#39;'
                break
              case 60: // <
                escape = '&lt;'
                break
              case 62: // >
                escape = '&gt;'
                break
              default:
                continue
            }

            if (lastIndex !== index) {
              html += str.substring(lastIndex, index)
            }

            lastIndex = index + 1
            html += escape
          }

          return lastIndex !== index
            ? html + str.substring(lastIndex, index)
            : html
        }

        /***/
      },
      /* 15 */
      /***/ function (module, exports, __webpack_require__) {
        var DOM = __webpack_require__(9)
        var utils = __webpack_require__(7)

        module.exports = (function () {
          var canvas = DOM.newEl('canvas')
          var ctx = null

          return function (sceneGraph) {
            if (ctx == null) {
              ctx = canvas.getContext('2d')
            }

            var dpr = utils.canvasRatio()
            var root = sceneGraph.root
            canvas.width = dpr * root.properties.width
            canvas.height = dpr * root.properties.height
            ctx.textBaseline = 'middle'

            var bg = root.children.holderBg
            var bgWidth = dpr * bg.width
            var bgHeight = dpr * bg.height
            //todo: parametrize outline width (e.g. in scene object)
            var outlineWidth = 2
            var outlineOffsetWidth = outlineWidth / 2

            ctx.fillStyle = bg.properties.fill
            ctx.fillRect(0, 0, bgWidth, bgHeight)

            if (bg.properties.outline) {
              //todo: abstract this into a method
              ctx.strokeStyle = bg.properties.outline.fill
              ctx.lineWidth = bg.properties.outline.width
              ctx.moveTo(outlineOffsetWidth, outlineOffsetWidth)
              // TL, TR, BR, BL
              ctx.lineTo(bgWidth - outlineOffsetWidth, outlineOffsetWidth)
              ctx.lineTo(
                bgWidth - outlineOffsetWidth,
                bgHeight - outlineOffsetWidth
              )
              ctx.lineTo(outlineOffsetWidth, bgHeight - outlineOffsetWidth)
              ctx.lineTo(outlineOffsetWidth, outlineOffsetWidth)
              // Diagonals
              ctx.moveTo(0, outlineOffsetWidth)
              ctx.lineTo(bgWidth, bgHeight - outlineOffsetWidth)
              ctx.moveTo(0, bgHeight - outlineOffsetWidth)
              ctx.lineTo(bgWidth, outlineOffsetWidth)
              ctx.stroke()
            }

            var textGroup = root.children.holderTextGroup
            ctx.font =
              textGroup.properties.font.weight +
              ' ' +
              dpr * textGroup.properties.font.size +
              textGroup.properties.font.units +
              ' ' +
              textGroup.properties.font.family +
              ', monospace'
            ctx.fillStyle = textGroup.properties.fill

            for (var lineKey in textGroup.children) {
              var line = textGroup.children[lineKey]
              for (var wordKey in line.children) {
                var word = line.children[wordKey]
                var x = dpr * (textGroup.x + line.x + word.x)
                var y =
                  dpr *
                  (textGroup.y +
                    line.y +
                    word.y +
                    textGroup.properties.leading / 2)

                ctx.fillText(word.properties.text, x, y)
              }
            }

            return canvas.toDataURL('image/png')
          }
        })()

        /***/
      },
      /******/
    ]
  )
})
;(function (ctx, isMeteorPackage) {
  if (isMeteorPackage) {
    Holder = ctx.Holder
  }
})(this, typeof Meteor !== 'undefined' && typeof Package !== 'undefined')
